package com.itcast.elasticsearch.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itcast.elasticsearch.bo.QueryGoodsBO;
import com.itcast.elasticsearch.converter.GoodsConverter;
import com.itcast.elasticsearch.dto.ListDataDTO;
import com.itcast.elasticsearch.entity.GoodsEntity;
import com.itcast.elasticsearch.index.GoodsIndex;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Max;
import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author sai
 * @version 1.0
 * @date 2021/8/20 15:21
 */
@Slf4j
@Service
public class ElasticsearchService {

    private GoodsService goodsService;
    private ElasticsearchRestTemplate template;
    private GoodsConverter goodsConverter;

    @Autowired
    public void setGoodsService(GoodsService goodsService) {
        this.goodsService = goodsService;
    }

    @Autowired
    public void setTemplate(ElasticsearchRestTemplate template) {
        this.template = template;
    }

    @Autowired(required = false)
    public void setGoodsConverter(GoodsConverter goodsConverter) {
        this.goodsConverter = goodsConverter;
    }

    /**
     * 同步商品数据
     * @return 操作成功返回true
     */
    public Boolean synGoods() {
        QueryGoodsBO queryGoodsBO = new QueryGoodsBO();
        queryGoodsBO.setSize(500);
        long current = 1;
        while (true) {
            // 1 分页查询商品
            queryGoodsBO.setCurrent(current++);
            Page<GoodsEntity> page = goodsService.queryByPage(queryGoodsBO);

            List<GoodsEntity> entityList = page.getRecords();
            if (CollectionUtils.isEmpty(entityList)) {
                log.info("synGoods - 分页查询完毕");
                break;
            }

            // 2 将数据同步到ES
            List<IndexQuery> queries = new ArrayList<>(entityList.size());
            for (GoodsEntity entity : entityList) {
                GoodsIndex goodsIndex = goodsConverter.entityToIndex(entity);
                queries.add(new IndexQueryBuilder()
                        .withObject(goodsIndex)
                        .build());
            }
            // 第一个参数我们实际添加的文档数据集合  第二个参数是对应我们索引映射的类
            template.bulkIndex(queries, GoodsIndex.class);
        }
        return Boolean.TRUE;
    }

    /**
     * 新增/修改 文档。
     * 当ID在索引库里存在时，修改
     * 当ID在索引库里不存在时，新增
     * @param goodsIndex 文档信息
     * @return 操作成功返回true
     */
    public Boolean save(GoodsIndex goodsIndex) {
        // 1 校验入参
        Assert.notNull(goodsIndex, "文档信息不能为空");
        Assert.notNull(goodsIndex.getId(), "文档ID不能为空");

        // 2 template
        template.save(goodsIndex);

        return Boolean.TRUE;
    }

    /**
     * 根据文档ID删除
     * @param id 文档ID
     * @return 操作成功返回true
     */
    public Boolean deleteById(String id) {
        // 校验
        Assert.hasText(id, "文档ID不能为空");

        template.delete(id, GoodsIndex.class);

        return Boolean.TRUE;
    }

    /**
     * match_all - 全部查询，默认返回10条数据
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> matchAll() {
        // 1 构建请求
        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();

        // 2 发出请求
        return sendRequest(new NativeSearchQuery(queryBuilder));
    }

    /**
     * 根据价格进行倒序排序返回
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> pageAndSort() {
        // 1 构建请求
        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
        NativeSearchQuery query = new NativeSearchQuery(queryBuilder);

        // 1.1 增加 分页，排序
        // 第一个参数-倒序 第二个参数-排序的字段
        Sort sort = Sort.by(Sort.Direction.DESC, "price");
        // 分页信息  page-第几页,默认0页开始  size-一页多少个文档
        PageRequest request = PageRequest.of(0, 10, sort);
        query.setPageable(request);

        // 2 发出请求
        return sendRequest(query);
    }

    /**
     * term查询
     * @param param 查询条件
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> termByBrand(String param) {
        // 校验
        Assert.hasText(param, "查询条件不能为空");

        // 构建请求
        // name-字段名 value-搜索条件
        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("brand", param);

        // 发送请求
        return sendRequest(new NativeSearchQuery(queryBuilder));
    }

    /**
     * math查询
     * @param param 查询条件
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> matchByTitle(String param) {
        // 校验
        Assert.hasText(param, "查询条件不能为空");

        // 构建请求
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", param);

        // 发送请求
        return sendRequest(new NativeSearchQuery(queryBuilder));
    }

    /**
     * wildcard查询
     * @param param 查询条件
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> wildcardByTitle(String param) {
        // 校验入参
        Assert.hasText(param, "查询条件不能为空");

        // 构建请求
        WildcardQueryBuilder queryBuilder = QueryBuilders.wildcardQuery("title", param);

        // 发送请求
        return sendRequest(new NativeSearchQuery(queryBuilder));
    }

    /**
     * range查询
     * @param start 起点
     * @param end 终点
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> rangeByPrice(int start, int end) {
        // 构建请求
        RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery("price");
        queryBuilder.gte(start)
                .lte(end);

        // 发送请求
        return sendRequest(new NativeSearchQuery(queryBuilder));
    }

    /**
     * 发送请求
     */
    private ListDataDTO<GoodsIndex> sendRequest(Query query) {
        SearchHits<GoodsIndex> searchHits = template.search(query, GoodsIndex.class);
        if (searchHits.getTotalHits() == 0) {
            return ListDataDTO.empty();
        }

        // 组装返回数据
        List<SearchHit<GoodsIndex>> searchHitList = searchHits.getSearchHits();
        List<GoodsIndex> goodsIndexList = new ArrayList<>(searchHitList.size());
        goodsIndexList.addAll(searchHitList.stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList()));
        return new ListDataDTO<>(searchHits.getTotalHits(), goodsIndexList);
    }

    /**
     * query_strng 查询
     * @param param 入参
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> queryString(String param) {
        // 校验
        Assert.hasText(param, "查询条件不能为空");

        // 构建请求
        QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery(param);
        queryBuilder.field("title").field("brand");

        // 发送请求
        return sendRequest(new NativeSearchQuery(queryBuilder));
    }

    /**
     * 查询 title 包含手机，并且 brand 是华为
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> bool() {
        // 构建请求
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("title", "手机"))
                .must(QueryBuilders.termQuery("brand", "华为"));

        // 发送请求
        return sendRequest(new NativeSearchQuery(queryBuilder));
    }

    /**
     * 指标聚合：找品牌是华为的商品中价格最高的商品价格
     * @return max_price
     */
    public BigDecimal maxPrice() {
        // 构建请求
        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("brand", "华为");
        // 指标聚合
        MaxAggregationBuilder aggregationBuilder = AggregationBuilders.max("max_price")
                .field("price");
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(aggregationBuilder)
                .withPageable(PageRequest.of(0, 1))
                .build();

        // 发送请求
        SearchHits<GoodsIndex> searchHits = template.search(query, GoodsIndex.class);

        // 组装返回数据
        Max max = Objects.requireNonNull(searchHits.getAggregations()).get("max_price");
        return BigDecimal.valueOf(max.getValue());
    }

    /**
     * 桶聚合：根据品牌聚合，看每个品牌的手机商品数据量
     * @return Map<String, Object> ： key-品牌  value-数量
     */
    public Map<Object, Object> brandNum() {
        // 构建请求
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "手机");
        // 桶聚合
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("brand_num")
                .field("brand");
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .addAggregation(aggregationBuilder)
                .withPageable(PageRequest.of(0, 1))
                .build();

        // 发送请求
        SearchHits<GoodsIndex> searchHits = template.search(query, GoodsIndex.class);
        Terms terms = Objects.requireNonNull(searchHits.getAggregations()).get("brand_num");

        // 组装返回数据
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        Map<Object, Object> map = new HashMap<>(buckets.size());
        for (Terms.Bucket bucket : buckets) {
            map.put(bucket.getKey(), bucket.getDocCount());
        }
        return map;
    }

    /**
     * 高亮： 让 title 中的“华为”和“手机”高亮起来
     * @return ListDataDTO<GoodsIndex>
     */
    public ListDataDTO<GoodsIndex> highlight() {
        // 构建请求
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "华为手机");
        // 高亮显示
        HighlightBuilder.Field field = new HighlightBuilder.Field("title")
                .preTags("<font class = 'color_class'>")
                .postTags("</font>");
        HighlightBuilder highlightBuilder = new HighlightBuilder().field(field);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withHighlightBuilder(highlightBuilder)
                .withPageable(PageRequest.of(0, 10))
                .build();

        // 发送请求
        SearchHits<GoodsIndex> searchHits = template.search(query, GoodsIndex.class);
        if (searchHits.getTotalHits() == 0) {
            log.info("highlight - 没有匹配到查询数据");
            return ListDataDTO.empty();
        }

        // 组装返回参数
        List<SearchHit<GoodsIndex>> searchHitList = searchHits.getSearchHits();
        List<GoodsIndex> goodsIndexList = new ArrayList<>(searchHitList.size());
        for (SearchHit<GoodsIndex> searchHit : searchHitList) {
            // 取出高亮数据
            List<String> titleList = searchHit.getHighlightField("title");
            GoodsIndex goodsIndex = searchHit.getContent();
            goodsIndex.setTitle(titleList.get(0));
            goodsIndexList.add(goodsIndex);
        }
        return new ListDataDTO<>(searchHits.getTotalHits(), goodsIndexList);
    }
}